<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="robots" content="noodp" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
        <title>《Flutter之precacheImage（）解析》 - Lucklyの博客 - 在阅读中遇见自己</title><meta name="Description" content="早起的年轻人做的网站"><meta property="og:title" content="《Flutter之precacheImage（）解析》" />
<meta property="og:description" content="
本文主要介绍Flutter 中 precacheImage() 方法是如何提高图片加载速度的？
" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://luckly.work/flutter%E4%B9%8Bprecacheimage%E8%A7%A3%E6%9E%90/" />
<meta property="og:image" content="https://luckly.work/logo.png"/>
<meta property="article:published_time" content="2021-07-31T21:12:42+08:00" />
<meta property="article:modified_time" content="2021-07-31T21:12:42+08:00" />
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://luckly.work/logo.png"/>

<meta name="twitter:title" content="《Flutter之precacheImage（）解析》"/>
<meta name="twitter:description" content="
本文主要介绍Flutter 中 precacheImage() 方法是如何提高图片加载速度的？
"/>
<meta name="application-name" content="LoveIt">
<meta name="apple-mobile-web-app-title" content="LoveIt"><meta name="theme-color" content="#ffffff"><meta name="msapplication-TileColor" content="#da532c"><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
        <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
        <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"><link rel="manifest" href="/site.webmanifest"><link rel="canonical" href="https://luckly.work/flutter%E4%B9%8Bprecacheimage%E8%A7%A3%E6%9E%90/" /><link rel="prev" href="https://luckly.work/flutter-drawframe/" /><link rel="next" href="https://luckly.work/flutter%E4%B9%8Bstack%E7%94%A8%E6%B3%95%E5%B0%8F%E7%BB%93/" /><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.min.css"><link rel="stylesheet" href="/css/style.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/css/all.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.7.2/animate.min.css"><script type="application/ld+json">
    {
        "@context": "http://schema.org",
        "@type": "BlogPosting",
        "headline": "《Flutter之precacheImage（）解析》",
        "inLanguage": "zh-CN",
        "mainEntityOfPage": {
            "@type": "WebPage",
            "@id": "https:\/\/luckly.work\/flutter%E4%B9%8Bprecacheimage%E8%A7%A3%E6%9E%90\/"
        },"image": [{
                            "@type": "ImageObject",
                            "url": "https:\/\/luckly.work\/images\/Apple-Devices-Preview.png",
                            "width":  3200 ,
                            "height":  2048 
                        }],"genre": "posts","keywords": "flutter","wordcount":  1570 ,
        "url": "https:\/\/luckly.work\/flutter%E4%B9%8Bprecacheimage%E8%A7%A3%E6%9E%90\/","datePublished": "2021-07-31T21:12:42+08:00","dateModified": "2021-07-31T21:12:42+08:00","license": "This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.","publisher": {
            "@type": "Organization",
            "name": "xxxx","logo": {
                    "@type": "ImageObject",
                    "url": "https:\/\/luckly.work\/images\/avatar.png",
                    "width":  528 ,
                    "height":  560 
                }},"author": {
                "@type": "Person",
                "name": "早起的年轻人"
            },"description": ""
    }
    </script></head>
    <body header-desktop="fixed" header-mobile="auto"><script type="text/javascript">(window.localStorage && localStorage.getItem('theme') ? localStorage.getItem('theme') === 'dark' : ('auto' === 'auto' ? window.matchMedia('(prefers-color-scheme: dark)').matches : 'auto' === 'dark')) && document.body.setAttribute('theme', 'dark');</script>

        <div id="mask"></div><div class="wrapper"><div class="logo-wrapper">
  <a href="/%20/" class="logo">Lucklyの博客 - 在阅读中遇见自己</a>
</div>

<nav class="site-navbar">
  <ul id="menu" class="menu">
    <li class="menu-item">
      <a class="menu-item-link" href="/posts/">所有文章</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/categories/flutter/">Flutter</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/categories/read/">读书笔记</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/categories/go/">Go</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/tags/">标签</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/categories/">分类</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/about/">关于</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="https://github.com/ITmxs">Github</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/friend/">友链</a>
    </li>
  </ul>
</nav><main class="main">
                <div class="container"><div class="toc" id="toc-auto">
            <h2 class="toc-title">目录</h2>
            <div class="toc-content" id="toc-content-auto"></div>
        </div><article class="page single"><h1 class="single-title animated flipInX">《Flutter之precacheImage（）解析》</h1><div class="post-meta">
            <div class="post-meta-line"><span class="post-author"><a href="/" title="Author" rel=" author" class="author"><i class="fas fa-user-circle fa-fw"></i>早起的年轻人</a></span>&nbsp;<span class="post-category">收录于 <a href="/categories/flutter/"><i class="far fa-folder fa-fw"></i>Flutter</a></span></div>
            <div class="post-meta-line"><i class="far fa-calendar-alt fa-fw"></i>&nbsp;<time datetime="2021-07-31">2021-07-31</time>&nbsp;<i class="fas fa-pencil-alt fa-fw"></i>&nbsp;约 1570 字&nbsp;
                <i class="far fa-clock fa-fw"></i>&nbsp;预计阅读 4 分钟&nbsp;</div>
        </div><div class="featured-image"><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png"
        data-srcset="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png, https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png 1.5x, https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png 2x"
        data-sizes="auto"
        alt="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png"
        title="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png" /></div><div class="details toc" id="toc-static"  kept="">
                <div class="details-summary toc-title">
                    <span>目录</span>
                    <span><i class="details-icon fas fa-angle-right"></i></span>
                </div>
                <div class="details-content toc-content" id="toc-content-static"><nav id="TableOfContents">
  <ul>
    <li><a href="#写缓存">写缓存</a>
      <ul>
        <li><a href="#imageproviderresolve"><code>ImageProvider.resolve()</code></a></li>
        <li><a href="#imagecacheputifabsent"><code>ImageCache.putIfAbsent()</code></a></li>
        <li><a href="#imageproviderload"><code>ImageProvider.load()</code></a></li>
      </ul>
    </li>
    <li><a href="#读缓存">读缓存</a></li>
    <li><a href="#总结">总结</a></li>
    <li><a href="#参考">参考</a></li>
  </ul>
</nav></div>
            </div><div class="content" id="content"><blockquote>
<p>本文主要介绍Flutter 中 <code>precacheImage()</code> 方法是如何提高图片加载速度的？</p>
</blockquote>
<p>Flutter 文档中提到调用 <a href="https://api.flutter.dev/flutter/widgets/precacheImage.html" target="_blank" rel="noopener noreffer">precacheImage()</a> 预先加载图片加载到缓存，后续如果这张图片被 <code>Image</code> 控件用到的话，加载起来会更快。</p>
<blockquote>
<p>Prefetches an image into the image cache.</p>
<p>If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage], it will probably be loaded faster.</p>
</blockquote>
<p><code>precacheImage()</code> 代码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">Future&lt;void&gt; precacheImage(
  ImageProvider provider,
  BuildContext context, {
  Size size,
  ImageErrorListener onError,
}) {
  final ImageConfiguration config = ...;
  final Completer&lt;void&gt; completer = Completer&lt;void&gt;();
  final ImageStream stream = provider.resolve(config);
  ...
  return completer.future;
}
</code></pre></td></tr></table>
</div>
</div><p><code>precacheImage()</code> 是如何加快图片加载速度的？我们可以从如下两个角度来考虑这个问题：</p>
<ul>
<li>写缓存 - 如何将图片加入缓存</li>
<li>读缓存 - 如何从缓存取出图片</li>
</ul>
<h1 id="写缓存">写缓存</h1>
<p>先上结论：<strong><code>precacheImage()</code> 调用 <code>ImageProvider.resolve()</code> 从缓存中取出图片</strong>。</p>
<p><code>precacheImage()</code> 主要的调用关系如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">precacheImage() -&gt;
 ImageProvider.resolve() -&gt;
  ImageCache.putIfAbsent() -&gt;
   ImageProvider.load() -&gt;
</code></pre></td></tr></table>
</div>
</div><h2 id="imageproviderresolve"><code>ImageProvider.resolve()</code></h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback"> ImageStream resolve(ImageConfiguration configuration) {
    ...;
    final ImageStream stream = ImageStream();
    T obtainedKey;

    key = obtainKey(configuration);
    key.then&lt;void&gt;((T key) {
      obtainedKey = key;
      final ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent(
        key,
        () =&gt; load(key, PaintingBinding.instance.instantiateImageCodec),
        onError: handleError,
      );
      if (completer != null) {
        stream.setCompleter(completer);
      }
    }).catchError(handleError);
    return stream;
}
</code></pre></td></tr></table>
</div>
</div><p>注意 <code>imageCache.putIfAbsent()</code> 第二个参数形式如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">ImageStreamCompleter putIfAbsent(
  Object key,
  ImageStreamCompleter loader(), 
  { ImageErrorListener onError })
</code></pre></td></tr></table>
</div>
</div><p>所以这里的 <code>loader</code> 参数实际上是对 <code>load()</code> 方法的封装。</p>
<h2 id="imagecacheputifabsent"><code>ImageCache.putIfAbsent()</code></h2>
<p><code>ImageCache</code> 用于缓存图片的类。这个类实现了 LRU 算法，最多可以保存1000个图片或者100MB。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">class ImageCache {
  final Map&lt;Object, _PendingImage&gt; _pendingImages = &lt;Object, _PendingImage&gt;{};
  final Map&lt;Object, _CachedImage&gt; _cache = &lt;Object, _CachedImage&gt;{};
</code></pre></td></tr></table>
</div>
</div><ul>
<li><code>_pendingImages</code> - 保存正在加载中的图片</li>
<li><code>_cache</code> - 保存已加载到缓存的图片</li>
</ul>
<p><code>putIfAbsent()</code> 方法是 <code>ImageCache</code> 主要的入口方法，代码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader()) {
  ImageStreamCompleter result = _pendingImages[key]?.completer;
  // Nothing needs to be done because the image hasn&#39;t loaded yet.
  if (result != null)
    return result;
  // Remove the provider from the list so that we can move it to the
  // recently used position below.
  final _CachedImage image = _cache.remove(key);
  if (image != null) {
    _cache[key] = image;
    return image.completer;
  }
  try {
    result = loader();
  } catch (error, stackTrace) {
    ...
  }
  void listener(ImageInfo info, bool syncCall) {
    // Images that fail to load don&#39;t contribute to cache size.
    final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
    final _CachedImage image = _CachedImage(result, imageSize);
    ...
    final _PendingImage pendingImage = _pendingImages.remove(key);
    if (pendingImage != null) {
      pendingImage.removeListener();
    }

    _cache[key] = image;
    ...
  }
  if (maximumSize &gt; 0 &amp;&amp; maximumSizeBytes &gt; 0) {
    final ImageStreamListener streamListener = ImageStreamListener(listener);
    _pendingImages[key] = _PendingImage(result, streamListener);
    // Listener is removed in [_PendingImage.removeListener].
    result.addListener(streamListener);
  }
  return result;
}
</code></pre></td></tr></table>
</div>
</div><p>其过程总结如下：</p>
<ul>
<li>
<p>如果 <code>_pendingImages</code> 存在对应的 key，说明正在加载该图片，此时什么也不用做所以直接返回</p>
</li>
<li>
<p>如果 <code>_cache</code> 存在对应的 key，说明已经加载过该图片，此时将图片调整到 LRU 位置并返回</p>
</li>
<li>
<p>否则，调用</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">loader()
</code></pre></td></tr></table>
</div>
</div><p>来加载图片。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">loader()
</code></pre></td></tr></table>
</div>
</div><p>的返回值为</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">result
</code></pre></td></tr></table>
</div>
</div><ul>
<li>为 <code>result</code> 创建新对象并保存到 <code>_pendingImages</code> 中</li>
<li>监听图片加载过程。加载成功后从 <code>_pendingImages</code> 移除对应的 key，并在 <code>_cache</code> 中保存加载结果</li>
</ul>
</li>
</ul>
<h2 id="imageproviderload"><code>ImageProvider.load()</code></h2>
<p><code>ImageProvider</code> 是一个抽象类，它的 <code>load()</code> 和 <code>obtainKey()</code> 是抽象方法，需要 <code>ImageProvider</code> 的子类实现。</p>
<p>以 <code>AssetBundleImageProvider</code> 为例，<code>load()</code> 方法如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">abstract class AssetBundleImageProvider extends ImageProvider&lt;AssetBundleImageKey&gt; {
  @override
  ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) {
    return MultiFrameImageStreamCompleter(
      codec: _loadAsync(key, decode),
      scale: key.scale,
      informationCollector: () sync* {
        yield DiagnosticsProperty&lt;ImageProvider&gt;(&#39;Image provider&#39;, this);
        yield DiagnosticsProperty&lt;AssetBundleImageKey&gt;(&#39;Image key&#39;, key);
      },
    );
  }
  
  @protected
  Future&lt;ui.Codec&gt; _loadAsync(AssetBundleImageKey key, DecoderCallback decode) async {
    final ByteData data = await key.bundle.load(key.name);
    if (data == null)
      throw &#39;Unable to read data&#39;;
    return await decode(data.buffer.asUint8List());
  }
}
</code></pre></td></tr></table>
</div>
</div><p>从代码不难看出这里的 <code>_loadAsync()</code> 即底层的 asset 图片加载过程，它由 <code>load()</code> 方法调用。而 <code>load()</code> 是前面 <code>result = loader();</code> 的具体执行过程。</p>
<p>至此可知，调用 <code>precache()</code> 可将指定 key 对应的 <code>_CachedImage</code> 保存到 <code>ImageCache</code> 中。</p>
<h1 id="读缓存">读缓存</h1>
<p>先上结论：<strong><code>Image</code> 调用 <code>ImageProvider.resolve()</code> 从缓存中取出图片</strong>。</p>
<p><code>Image</code> 控件用于显示图片。有多种方式显示图片：</p>
<ul>
<li><code>Image.asset</code> - 使用 key 从 <code>AssetBundle</code> 图片</li>
<li><code>Image.network()</code> - 使用 url 从网络加载图片</li>
<li><code>Image.file</code> - 从文件加载图片</li>
<li><code>Image.memory</code> - 从内存加载图片</li>
</ul>
<p>这些显示方式都有各自的 <code>ImageProvider</code>：</p>
<ul>
<li><code>AssetImage</code></li>
<li><code>NetworkImage</code></li>
<li><code>FileImage</code></li>
<li><code>MemoryImage</code></li>
</ul>
<p>上述这些 Image 都继承自 <code>ImageProvider</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">abstract class ImageProvider&lt;T&gt; {
  ImageStream resolve(ImageConfiguration configuration) {}
  Future&lt;bool&gt; evict({ ImageCache cache, ImageConfiguration configuration = ImageConfiguration.empty }) async {}
  Future&lt;T&gt; obtainKey(ImageConfiguration configuration);
  ImageStreamCompleter load(T key, DecoderCallback decode);
}

abstract class AssetBundleImageProvider extends ImageProvider&lt;AssetBundleImageKey&gt; {}

abstract class NetworkImage extends ImageProvider&lt;NetworkImage&gt; {

class FileImage extends ImageProvider&lt;FileImage&gt; {

class MemoryImage extends ImageProvider&lt;MemoryImage&gt; {}
</code></pre></td></tr></table>
</div>
</div><p><code>Image</code> 它是一个 <code>StatefulWidget</code>，它依赖于其 <code>ImageProvider</code> 成员，主要代码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">class Image extends StatefulWidget {
 final ImageProvider image;
  _ImageState createState() =&gt; _ImageState();
}

class _ImageState extends State&lt;Image&gt; with WidgetsBindingObserver {
  @override
  void didChangeDependencies() {
    ...
    _resolveImage();
    ...
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(Image oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.image != oldWidget.image)
      _resolveImage();    
  }
  
  void _resolveImage() {
    final ImageStream newStream =
      widget.image.resolve(createLocalImageConfiguration(
        context,
        size: widget.width != null &amp;&amp; widget.height != null ? Size(widget.width, widget.height) : null,
      ));
    _updateSourceStream(newStream);
  }
}
</code></pre></td></tr></table>
</div>
</div><p>注意这里的 <code>_resolveImage()</code> 方法，它调用 <code>ImageProvider.resolve()</code>。所以可以认为是由 <code>ImageProvider.resolve()</code> 从缓存中取出图片(前面分析过 <code>ImageProvider.resolve()</code> 与图片缓存的关系)。</p>
<h1 id="总结">总结</h1>
<p><code>precacheImage()</code> 提升图片加载速度的关键其实在于主动调用 <code>ImageProvider.resolve()</code> 来提前将图片加载到 <code>ImageCache</code>，示意图如下(这里假设从本地读取图片)：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://luckly007.oss-cn-beijing.aliyuncs.com/img/15814253180199.jpg"
        data-srcset="https://luckly007.oss-cn-beijing.aliyuncs.com/img/15814253180199.jpg, https://luckly007.oss-cn-beijing.aliyuncs.com/img/15814253180199.jpg 1.5x, https://luckly007.oss-cn-beijing.aliyuncs.com/img/15814253180199.jpg 2x"
        data-sizes="auto"
        alt="https://luckly007.oss-cn-beijing.aliyuncs.com/img/15814253180199.jpg"
        title="-w850" /></p>
<p>如果后续有 <code>Image</code> 显示同一个 key 对应的图片，再次调用 <code>ImageProvider.resolve()</code> 的速度将大为提升(网络图片会尤其明显)，当然前提是该图片仍然在图片缓存中。</p>
<h1 id="参考">参考</h1>
<ul>
<li><a href="https://api.flutter.dev/flutter/widgets/precacheImage.html" target="_blank" rel="noopener noreffer">precacheImage function - widgets library - Dart API</a></li>
</ul></div><div class="post-footer" id="post-footer">
    <div class="post-info">
        <div class="post-info-line">
            <div class="post-info-mod">
                <span>更新于 2021-07-31</span>
            </div>
            <div class="post-info-license"></div>
        </div>
        <div class="post-info-line">
            <div class="post-info-md"></div>
            <div class="post-info-share">
                <span><a href="javascript:void(0);" title="分享到 Twitter" data-sharer="twitter" data-url="https://luckly.work/flutter%E4%B9%8Bprecacheimage%E8%A7%A3%E6%9E%90/" data-title="《Flutter之precacheImage（）解析》" data-hashtags="flutter"><i class="fab fa-twitter fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Facebook" data-sharer="facebook" data-url="https://luckly.work/flutter%E4%B9%8Bprecacheimage%E8%A7%A3%E6%9E%90/" data-hashtag="flutter"><i class="fab fa-facebook-square fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Hacker News" data-sharer="hackernews" data-url="https://luckly.work/flutter%E4%B9%8Bprecacheimage%E8%A7%A3%E6%9E%90/" data-title="《Flutter之precacheImage（）解析》"><i class="fab fa-hacker-news fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Line" data-sharer="line" data-url="https://luckly.work/flutter%E4%B9%8Bprecacheimage%E8%A7%A3%E6%9E%90/" data-title="《Flutter之precacheImage（）解析》"><i data-svg-src="https://cdn.jsdelivr.net/npm/simple-icons@2.14.0/icons/line.svg"></i></a><a href="javascript:void(0);" title="分享到 微博" data-sharer="weibo" data-url="https://luckly.work/flutter%E4%B9%8Bprecacheimage%E8%A7%A3%E6%9E%90/" data-title="《Flutter之precacheImage（）解析》" data-image="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png"><i class="fab fa-weibo fa-fw"></i></a></span>
            </div>
        </div>
    </div>

    <div class="post-info-more">
        <section class="post-tags"><i class="fas fa-tags fa-fw"></i>&nbsp;<a href="/tags/flutter/">flutter</a></section>
        <section>
            <span><a href="javascript:void(0);" onclick="window.history.back();">返回</a></span>&nbsp;|&nbsp;<span><a href="/">主页</a></span>
        </section>
    </div>

    <div class="post-nav"><a href="/flutter-drawframe/" class="prev" rel="prev" title="《Flutter DrawFrame（）》"><i class="fas fa-angle-left fa-fw"></i>《Flutter DrawFrame（）》</a>
            <a href="/flutter%E4%B9%8Bstack%E7%94%A8%E6%B3%95%E5%B0%8F%E7%BB%93/" class="next" rel="next" title="《Flutter之Stack用法小结》">《Flutter之Stack用法小结》<i class="fas fa-angle-right fa-fw"></i></a></div>
</div>
</article></div>
            </main><footer class="footer">
        <div class="footer-container"><div class="footer-line">由 <a href="https://gohugo.io/" target="_blank" rel="noopener noreffer" title="Hugo 0.79.1">Hugo</a> 强力驱动 | 主题 - <a href="https://github.com/dillonzq/LoveIt" target="_blank" rel="noopener noreffer" title="LoveIt 0.2.10"><i class="far fa-kiss-wink-heart fa-fw"></i> LoveIt</a>
                </div><div class="footer-line"><i class="far fa-copyright fa-fw"></i><span itemprop="copyrightYear">2021</span><span class="author" itemprop="copyrightHolder">&nbsp;<a href="/" target="_blank">早起的年轻人</a></span>&nbsp;|&nbsp;<span class="license"><a rel="license external nofollow noopener noreffer" href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank">CC BY-NC 4.0</a></span></div>
        </div>
    </footer></div>

        <div id="fixed-buttons"><a href="#" id="back-to-top" class="fixed-button" title="回到顶部">
                <i class="fas fa-arrow-up fa-fw"></i>
            </a><a href="#" id="view-comments" class="fixed-button" title="查看评论">
                <i class="fas fa-comment fa-fw"></i>
            </a>
        </div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cookieconsent@3.1.1/build/cookieconsent.min.css"><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/smooth-scroll@16.1.3/dist/smooth-scroll.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/autocomplete.js@0.37.1/dist/autocomplete.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/algoliasearch@4.2.0/dist/algoliasearch-lite.umd.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/lazysizes@5.2.2/lazysizes.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/sharer.js@0.4.0/sharer.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/cookieconsent@3.1.1/build/cookieconsent.min.js"></script><script type="text/javascript">window.config={"code":{"copyTitle":"复制到剪贴板","maxShownLines":10},"comment":{},"cookieconsent":{"content":{"dismiss":"同意","link":"了解更多","message":"本网站使用 Cookies 来改善您的浏览体验."},"enable":true,"palette":{"button":{"background":"#f0f0f0"},"popup":{"background":"#1aa3ff"}},"theme":"edgeless"},"search":{"algoliaAppID":"PASDMWALPK","algoliaIndex":"index.zh-cn","algoliaSearchKey":"b42948e51daaa93df92381c8e2ac0f93","highlightTag":"em","maxResultLength":10,"noResultsFound":"没有找到结果","snippetLength":50,"type":"algolia"}};</script><script type="text/javascript" src="/js/theme.min.js"></script></body>
</html>
